/**
 * Copyright 2013 wasu.com
 *
 *
 * Create on 2014-7-29 下午11:11:53
 */
package xiaojian.toolkit.netty.handler.codec.tcp.decode;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.CommonsHeader;
import xiaojian.toolkit.bean.util.meta.Int2TypeMetainfo;
import xiaojian.toolkit.util.ByteUtils;
import xiaojian.toolkit.util.IdentifierFactory;
import xiaojian.toolkit.util.MutableIdentifyable;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.AttributeKey;

/**
 * 解析正文为json格式，头为xipheader的数据报文。
 * 
 * @author <a href="mailto:caoxiaojian@wasu.com>xiaojian.cao</a>
 * 
 */
public abstract class MixedJsonDecoder extends ByteToMessageDecoder {

	private static final Logger logger = LoggerFactory.getLogger(MixedJsonDecoder.class);

	private static final AttributeKey<CommonsHeader> HEADER_ATTRIBUTE_KEY = AttributeKey
			.valueOf("XIP_HEADER_ATTRIBUTE_KEY");

	// 大于1M的数据包可能是问题数据包
	private int maxMessageLength = 1024 * 1024;

	private int dumpBytes = 256;
	private boolean isDebugEnabled;

	private Int2TypeMetainfo jsonTypeMetainfo;

	/**
	 * @see io.netty.handler.codec.ByteToMessageDecoder#decode(io.netty.channel.ChannelHandlerContext,
	 *      io.netty.buffer.ByteBuf, java.util.List)
	 */
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
		CommonsHeader header = ctx.channel().attr(HEADER_ATTRIBUTE_KEY).get();
		if (null == header) {
			if (buffer.readableBytes() < CommonsHeader.HEADER_LENGTH) {
				return;
			}

			if (logger.isDebugEnabled() && isDebugEnabled) {
				logger.debug("parse header... try parse...");
			}

			header = new CommonsHeader();
			header.setBasicVer(buffer.readByte());
			header.setPayloadType(buffer.readShort());
			header.setType(buffer.readShort());
			
			// -- identifier
			short idLength = buffer.readShort();
			header.setIdBytesLen(idLength);
			
			byte[] idContentBytes = new byte[idLength];
			buffer.readBytes(idContentBytes);
			header.setIdBytes(idContentBytes);
			
			header.setReserved(buffer.readInt());
			header.setMessageCode(buffer.readInt());
			header.setMessageLength(buffer.readInt());

			if (logger.isDebugEnabled() && isDebugEnabled) {
				logger.debug("header-->" + header);
			}

			if (header.getMessageLength() < 0) {
				logger.error("header.messageLength ({}) < 0, so drop this connection {}.", header.getMessageLength(),
						ctx.channel());
				ctx.channel().close().await();
				throw new RuntimeException("messageLength too large:" + header.getMessageLength());
			}
			if (maxMessageLength > 0) {
				if (header.getMessageLength() > maxMessageLength) {
					logger.error("header.messageLength ({}) exceed maxMessageLength[{}], so drop this connection{}.",
							header.getMessageLength(), maxMessageLength, ctx.channel());
					ctx.channel().close().await();
					throw new RuntimeException("messageLength too large:" + header.getMessageLength());
				}
			}

			ctx.channel().attr(HEADER_ATTRIBUTE_KEY).set(header);
		}

		if (buffer.readableBytes() < header.getMessageLength()) {
			logger.debug("readableBytes {} smaller than packageLength {},waiting for remain bytes",
					buffer.readableBytes(), header.getMessageLength());
			return;
		}

		// 为下一次在同一ctx上进行xip接受初始化环境
		ctx.channel().attr(HEADER_ATTRIBUTE_KEY).remove();

		final byte[] bytes = new byte[header.getMessageLength()];
		buffer.readBytes(bytes);

		if (logger.isTraceEnabled() && isDebugEnabled) {
			logger.trace("body raw bytes \r\n{}", ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		int msgCode = header.getMessageCode();
		Class<?> clazz = jsonTypeMetainfo.find(msgCode);
		if (clazz == null) {
			throw new RuntimeException("invalid msgcode {" + msgCode + "}");
		}

		String jsonString = new String(bytes, "UTF-8");
		if (logger.isDebugEnabled() && isDebugEnabled) {
			logger.debug("jsonString-->" + jsonString);
		}

		// final Object outObj = JSON.parseObject(jsonString, clazz);

		Object outObj = parseJsonString(jsonString, clazz);
		if (null != outObj) {
			if (outObj instanceof MutableIdentifyable) {
				((MutableIdentifyable) outObj)
						.setIdentification(IdentifierFactory.fromByte(header.getIdBytes()));;
			}
			out.add(outObj);
		}

	}

	protected abstract Object parseJsonString(String jsonString, Class<?> clazz);

	/**
	 * @return the maxMessageLength
	 */
	public int getMaxMessageLength() {
		return maxMessageLength;
	}

	/**
	 * @param maxMessageLength
	 *            the maxMessageLength to set
	 */
	public void setMaxMessageLength(int maxMessageLength) {
		this.maxMessageLength = maxMessageLength;
	}

	/**
	 * @return the dumpBytes
	 */
	public int getDumpBytes() {
		return dumpBytes;
	}

	/**
	 * @param dumpBytes
	 *            the dumpBytes to set
	 */
	public void setDumpBytes(int dumpBytes) {
		this.dumpBytes = dumpBytes;
	}

	/**
	 * @return the isDebugEnabled
	 */
	public boolean isDebugEnabled() {
		return isDebugEnabled;
	}

	/**
	 * @param isDebugEnabled
	 *            the isDebugEnabled to set
	 */
	public void setDebugEnabled(boolean isDebugEnabled) {
		this.isDebugEnabled = isDebugEnabled;
	}

	/**
	 * @return the jsonTypeMetainfo
	 */
	public Int2TypeMetainfo getJsonTypeMetainfo() {
		return jsonTypeMetainfo;
	}

	/**
	 * @param jsonTypeMetainfo
	 *            the jsonTypeMetainfo to set
	 */
	public void setJsonTypeMetainfo(Int2TypeMetainfo jsonTypeMetainfo) {
		this.jsonTypeMetainfo = jsonTypeMetainfo;
	}

}
